package com.j256.ormlite.table;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.j256.ormlite.dao.BaseDaoImpl;
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.dao.DaoManager;
import com.j256.ormlite.db.DatabaseType;
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.field.FieldType;
import com.j256.ormlite.logger.Logger;
import com.j256.ormlite.logger.LoggerFactory;
import com.j256.ormlite.misc.IOUtils;
import com.j256.ormlite.misc.SqlExceptionUtil;
import com.j256.ormlite.stmt.StatementBuilder.StatementType;
import com.j256.ormlite.support.CompiledStatement;
import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.support.DatabaseConnection;
import com.j256.ormlite.support.DatabaseResults;

/**
 * Couple utility methods for the creating, dropping, and maintenance of tables.
 * 
 * @author graywatson
 */
public class TableUtils {

	private static Logger logger = LoggerFactory.getLogger(TableUtils.class);
	private static final FieldType[] noFieldTypes = new FieldType[0];

	/**
	 * For static methods only.
	 */
	private TableUtils() {
	}

	/**
	 * Issue the database statements to create the table associated with a class.
	 * 根据数据库表实体模型，创建表
	 * 
	 * @param connectionSource
	 *            Associated connection source.
	 * @param dataClass
	 *            The class for which a table will be created.
	 * @return The number of statements executed to do so.
	 */
	public static <T> int createTable(ConnectionSource connectionSource, Class<T> dataClass) throws SQLException {
		//根据数据库表实体模型，创建Dao层实例
		Dao<T, ?> dao = DaoManager.createDao(connectionSource, dataClass);
		//使用Dao对象创建表
		return doCreateTable(dao, false);
	}

	/**
	 * Issue the database statements to create the table associated with a table configuration.
	 * 
	 * @param dao
	 *            Associated dao.
	 * @return The number of statements executed to do so.
	 */
	public static int createTable(Dao<?, ?> dao) throws SQLException {
		return doCreateTable(dao, false);
	}

	/**
	 * Create a table if it does not already exist. This is not supported by all databases.
	 */
	public static <T> int createTableIfNotExists(ConnectionSource connectionSource, Class<T> dataClass)
			throws SQLException {
		Dao<T, ?> dao = DaoManager.createDao(connectionSource, dataClass);
		return doCreateTable(dao, true);
	}

	/**
	 * Issue the database statements to create the table associated with a table configuration.
	 * 
	 * @param connectionSource
	 *            connectionSource Associated connection source.
	 * @param tableConfig
	 *            Hand or spring wired table configuration. If null then the class must have {@link DatabaseField}
	 *            annotations.
	 * @return The number of statements executed to do so.
	 */
	public static <T> int createTable(ConnectionSource connectionSource, DatabaseTableConfig<T> tableConfig)
			throws SQLException {
		Dao<T, ?> dao = DaoManager.createDao(connectionSource, tableConfig);
		return doCreateTable(dao, false);
	}

	/**
	 * Create a table if it does not already exist. This is not supported by all databases.
	 */
	public static <T> int createTableIfNotExists(ConnectionSource connectionSource, DatabaseTableConfig<T> tableConfig)
			throws SQLException {
		Dao<T, ?> dao = DaoManager.createDao(connectionSource, tableConfig);
		return doCreateTable(dao, true);
	}

	/**
	 * Return an list of SQL statements that need to be run to create a table. To do the work of creating, you should
	 * call {@link #createTable}.
	 * 
	 * @param connectionSource
	 *            Our connect source which is used to get the database type, not to apply the creates.
	 * @param dataClass
	 *            Class of the entity to create statements that will create the table.
	 * @return A list of table create statements.
	 */
	public static <T, ID> List<String> getCreateTableStatements(ConnectionSource connectionSource, Class<T> dataClass)
			throws SQLException {
		List<String> statementList = new ArrayList<String>();
		Dao<T, ?> dao = DaoManager.createDao(connectionSource, dataClass);
		addCreateTableStatements(connectionSource.getDatabaseType(), dao.getTableInfo(), statementList, statementList,
				false, false);
		return statementList;
	}

	/**
	 * Return an list of SQL statements that need to be run to create a table. To do the work of creating, you should
	 * call {@link #createTable}.
	 * 
	 * @param connectionSource
	 *            Our connect source which is used to get the database type, not to apply the creates.
	 * @param tableConfig
	 *            Hand or spring wired table configuration. If null then the class must have {@link DatabaseField}
	 *            annotations.
	 * @return The list of table create statements.
	 */
	public static <T, ID> List<String> getCreateTableStatements(ConnectionSource connectionSource,
			DatabaseTableConfig<T> tableConfig) throws SQLException {
		Dao<T, ID> dao = DaoManager.createDao(connectionSource, tableConfig);
		DatabaseType databaseType = connectionSource.getDatabaseType();
		if (dao instanceof BaseDaoImpl<?, ?>) {
			return addCreateTableStatements(databaseType, ((BaseDaoImpl<?, ?>) dao).getTableInfo(), false, false);
		} else {
			tableConfig.extractFieldTypes(databaseType);
			TableInfo<T, ID> tableInfo = new TableInfo<T, ID>(databaseType, tableConfig);
			return addCreateTableStatements(databaseType, tableInfo, false, false);
		}
	}

	/**
	 * This method does not properly handle complex types, especially anything with foreign objects.
	 * 
	 * @deprecated Please use {@link #getCreateTableStatements(ConnectionSource, Class)}.
	 */
	@Deprecated
	public static <T> List<String> getCreateTableStatements(DatabaseType databaseType, Class<T> dataClass)
			throws SQLException {
		List<String> statementList = new ArrayList<String>();
		TableInfo<T, Object> tableInfo = new TableInfo<T, Object>(databaseType, dataClass);
		addCreateTableStatements(databaseType, tableInfo, statementList, statementList, false, false);
		return statementList;
	}

	/**
	 * 删除表
	 * Issue the database statements to drop the table associated with a class.
	 * 
	 * <p>
	 * <b>WARNING:</b> This is [obviously] very destructive and is unrecoverable.
	 * </p>
	 * 
	 * @param connectionSource
	 *            Associated connection source.
	 * @param dataClass
	 *            The class for which a table will be dropped.
	 * @param ignoreErrors
	 *            If set to true then try each statement regardless of {@link SQLException} thrown previously.
	 * @return The number of statements executed to do so.
	 */
	public static <T, ID> int dropTable(ConnectionSource connectionSource, Class<T> dataClass, boolean ignoreErrors)
			throws SQLException {
		//根据数据库表实体模型，创建Dao层实例
		Dao<T, ID> dao = DaoManager.createDao(connectionSource, dataClass);
		//使用Dao对象删除表
		return dropTable(dao, ignoreErrors);
	}

	/**
	 * 删除表
	 * Issue the database statements to drop the table associated with a dao.
	 * 
	 * @param dao
	 *            Associated dao.
	 * @return The number of statements executed to do so.
	 */
	public static <T, ID> int dropTable(Dao<T, ID> dao, boolean ignoreErrors) throws SQLException {
		//获取数据库连接
		ConnectionSource connectionSource = dao.getConnectionSource();
		//获取数据库表实体Class
		Class<T> dataClass = dao.getDataClass();
		DatabaseType databaseType = connectionSource.getDatabaseType();
		//获取表信息，删除表
		if (dao instanceof BaseDaoImpl<?, ?>) {
			return doDropTable(databaseType, connectionSource, ((BaseDaoImpl<?, ?>) dao).getTableInfo(), ignoreErrors);
		} else {
			TableInfo<T, ID> tableInfo = new TableInfo<T, ID>(databaseType, dataClass);
			return doDropTable(databaseType, connectionSource, tableInfo, ignoreErrors);
		}
	}

	/**
	 * 删除表
	 * Issue the database statements to drop the table associated with a table configuration.
	 * 
	 * <p>
	 * <b>WARNING:</b> This is [obviously] very destructive and is unrecoverable.
	 * </p>
	 * 
	 * @param connectionSource
	 *            Associated connection source.
	 * @param tableConfig
	 *            Hand or spring wired table configuration. If null then the class must have {@link DatabaseField}
	 *            annotations.
	 * @param ignoreErrors
	 *            If set to true then try each statement regardless of {@link SQLException} thrown previously.
	 * @return The number of statements executed to do so.
	 */
	public static <T, ID> int dropTable(ConnectionSource connectionSource, DatabaseTableConfig<T> tableConfig,
			boolean ignoreErrors) throws SQLException {
		DatabaseType databaseType = connectionSource.getDatabaseType();
		Dao<T, ID> dao = DaoManager.createDao(connectionSource, tableConfig);
		if (dao instanceof BaseDaoImpl<?, ?>) {
			return doDropTable(databaseType, connectionSource, ((BaseDaoImpl<?, ?>) dao).getTableInfo(), ignoreErrors);
		} else {
			tableConfig.extractFieldTypes(databaseType);
			TableInfo<T, ID> tableInfo = new TableInfo<T, ID>(databaseType, tableConfig);
			return doDropTable(databaseType, connectionSource, tableInfo, ignoreErrors);
		}
	}

	/**
	 * 清空表数据
	 * Clear all data out of the table. For certain database types and with large sized tables, which may take a long
	 * time. In some configurations, it may be faster to drop and re-create the table.
	 * 
	 * <p>
	 * <b>WARNING:</b> This is [obviously] very destructive and is unrecoverable.
	 * </p>
	 */
	public static <T> int clearTable(ConnectionSource connectionSource, Class<T> dataClass) throws SQLException {
		DatabaseType databaseType = connectionSource.getDatabaseType();
		String tableName = DatabaseTableConfig.extractTableName(databaseType, dataClass);
		String schemaName = DatabaseTableConfig.extractSchemaName(dataClass);
		if (databaseType.isEntityNamesMustBeUpCase()) {
			tableName = databaseType.upCaseEntityName(tableName);
		}
		return clearTable(connectionSource, schemaName, tableName);
	}

	/**
	 * 清空表数据
	 * Clear all data out of the table. For certain database types and with large sized tables, which may take a long
	 * time. In some configurations, it may be faster to drop and re-create the table.
	 * 
	 * <p>
	 * <b>WARNING:</b> This is [obviously] very destructive and is unrecoverable.
	 * </p>
	 */
	public static <T> int clearTable(ConnectionSource connectionSource, DatabaseTableConfig<T> tableConfig)
			throws SQLException {
		//开始清空表数据
		return clearTable(connectionSource, tableConfig.getSchemaName(), tableConfig.getTableName());
	}

	/**
	 * 清空表数据
	 */
	private static <T> int clearTable(ConnectionSource connectionSource, String schemaName, String tableName)
			throws SQLException {
		DatabaseType databaseType = connectionSource.getDatabaseType();
		StringBuilder sb = new StringBuilder(48);
		if (databaseType.isTruncateSupported()) {
			sb.append("TRUNCATE TABLE ");
		} else {
			sb.append("DELETE FROM ");
		}
		if (schemaName != null && schemaName.length() > 0) {
			databaseType.appendEscapedEntityName(sb, schemaName);
			sb.append('.');
		}
		databaseType.appendEscapedEntityName(sb, tableName);
		String statement = sb.toString();
		logger.info("clearing table '{}' with '{}", tableName, statement);
		CompiledStatement compiledStmt = null;
		//获取数据库连接
		DatabaseConnection connection = connectionSource.getReadWriteConnection(tableName);
		try {
			//预编译SQL
			compiledStmt = connection.compileStatement(statement, StatementType.EXECUTE, noFieldTypes,
					DatabaseConnection.DEFAULT_RESULT_FLAGS, false);
			//执行SQL
			return compiledStmt.runExecute();
		} finally {
			//回收数据库连接
			IOUtils.closeThrowSqlException(compiledStmt, "compiled statement");
			connectionSource.releaseConnection(connection);
		}
	}

	/**
	 * 根据表信息，删除表
	 */
	private static <T, ID> int doDropTable(DatabaseType databaseType, ConnectionSource connectionSource,
			TableInfo<T, ID> tableInfo, boolean ignoreErrors) throws SQLException {
		List<String> statements = new ArrayList<String>();
		//添加删除表索引SQL
		addDropIndexStatements(databaseType, tableInfo, statements, true);
		//添加删除表SQL
		addDropTableStatements(databaseType, tableInfo, statements, true);
		//获取数据库连接
		DatabaseConnection connection = connectionSource.getReadWriteConnection(tableInfo.getTableName());
		try {
			//执行SQL
			return doStatements(connection, "drop", statements, ignoreErrors,
					databaseType.isCreateTableReturnsNegative(), false);
		} finally {
			//回收数据库连接
			connectionSource.releaseConnection(connection);
		}
	}

	/**
	 * 添加删除表索引SQL
	 */
	private static <T, ID> void addDropIndexStatements(DatabaseType databaseType, TableInfo<T, ID> tableInfo,
			List<String> statements, boolean logDetails) {
		// run through and look for index annotations
		Set<String> indexSet = new HashSet<String>();
		//获取表字段信息，添加要删除的索引名称
		for (FieldType fieldType : tableInfo.getFieldTypes()) {
			String indexName = fieldType.getIndexName();
			if (indexName != null) {
				indexSet.add(indexName);
			}
			String uniqueIndexName = fieldType.getUniqueIndexName();
			if (uniqueIndexName != null) {
				indexSet.add(uniqueIndexName);
			}
		}

		//开始拼接SQL
		StringBuilder sb = new StringBuilder(48);
		for (String indexName : indexSet) {
			logger.info("dropping index '{}' for table '{}", indexName, tableInfo.getTableName());
			sb.append("DROP INDEX ");
			databaseType.appendEscapedEntityName(sb, indexName);
			statements.add(sb.toString());
			sb.setLength(0);
		}
	}

	/**
	 * Generate and return the list of statements to drop a database table.
	 * 添加删除表SQL
	 */
	private static <T, ID> void addDropTableStatements(DatabaseType databaseType, TableInfo<T, ID> tableInfo,
			List<String> statements, boolean logDetails) {
		List<String> statementsBefore = new ArrayList<String>();
		List<String> statementsAfter = new ArrayList<String>();
		//获取表字段信息，添加要删除的列和参数
		for (FieldType fieldType : tableInfo.getFieldTypes()) {
			databaseType.dropColumnArg(fieldType, statementsBefore, statementsAfter);
		}
		//开始拼接SQL
		StringBuilder sb = new StringBuilder(64);
		if (logDetails) {
			logger.info("dropping table '{}'", tableInfo.getTableName());
		}
		sb.append("DROP TABLE ");
		if (tableInfo.getSchemaName() != null && tableInfo.getSchemaName().length() > 0) {
			databaseType.appendEscapedEntityName(sb, tableInfo.getSchemaName());
			sb.append('.');
		}
		databaseType.appendEscapedEntityName(sb, tableInfo.getTableName());
		//拼接结束
		sb.append(' ');
		statements.addAll(statementsBefore);
		statements.add(sb.toString());
		statements.addAll(statementsAfter);
	}

	/**
	 * 使用Dao对象创建表
	 */
	private static <T, ID> int doCreateTable(Dao<T, ID> dao, boolean createIfNotExists) throws SQLException {
		ConnectionSource connectionSource = dao.getConnectionSource();
		DatabaseType databaseType = connectionSource.getDatabaseType();
		//如果Dao层对象继承于BaseDaoImpl，那么获取它配置的表信息进行创建表
		if (dao instanceof BaseDaoImpl<?, ?>) {
			return doCreateTable(connectionSource, ((BaseDaoImpl<?, ?>) dao).getTableInfo(), createIfNotExists);
		} else {
			//通过数据库表实体信息，进行创建表
			TableInfo<T, ID> tableInfo = new TableInfo<T, ID>(databaseType, dao.getDataClass());
			return doCreateTable(connectionSource, tableInfo, createIfNotExists);
		}
	}

	/**
	 * 根据表信息，创建表
	 */
	private static <T, ID> int doCreateTable(ConnectionSource connectionSource, TableInfo<T, ID> tableInfo,
			boolean createIfNotExists) throws SQLException {
		DatabaseType databaseType = connectionSource.getDatabaseType();
		List<String> statements = new ArrayList<String>();
		List<String> queriesAfter = new ArrayList<String>();
		//生成创建表的SQL语句
		addCreateTableStatements(databaseType, tableInfo, statements, queriesAfter, createIfNotExists, true);
		//获取数据库连接
		DatabaseConnection connection = connectionSource.getReadWriteConnection(tableInfo.getTableName());
		try {
			//执行SQL语句
			int stmtC = doStatements(connection, "create", statements, false,
					databaseType.isCreateTableReturnsNegative(), databaseType.isCreateTableReturnsZero());
			stmtC += doCreateTestQueries(connection, databaseType, queriesAfter);
			return stmtC;
		} finally {
			//回收数据库连接
			connectionSource.releaseConnection(connection);
		}
	}

	/**
	 * 执行SQL语句
	 */
	private static int doStatements(DatabaseConnection connection, String label, Collection<String> statements,
			boolean ignoreErrors, boolean returnsNegative, boolean expectingZero) throws SQLException {
		int stmtC = 0;
		for (String statement : statements) {
			int rowC = 0;
			CompiledStatement compiledStmt = null;
			try {
				//SQL预编译
				compiledStmt = connection.compileStatement(statement, StatementType.EXECUTE, noFieldTypes,
						DatabaseConnection.DEFAULT_RESULT_FLAGS, false);
				//执行SQL，返回值为本次执行影响多少行数据
				rowC = compiledStmt.runExecute();
				logger.info("executed {} table statement changed {} rows: {}", label, rowC, statement);
			} catch (SQLException e) {
				if (ignoreErrors) {
					logger.info("ignoring {} error '{}' for statement: {}", label, e, statement);
				} else {
					throw SqlExceptionUtil.create("SQL statement failed: " + statement, e);
				}
			} finally {
				IOUtils.closeThrowSqlException(compiledStmt, "compiled statement");
			}
			//安全检查
			// sanity check
			if (rowC < 0) {
				if (!returnsNegative) {
					throw new SQLException(
							"SQL statement " + statement + " updated " + rowC + " rows, we were expecting >= 0");
				}
			} else if (rowC > 0 && expectingZero) {
				throw new SQLException("SQL statement updated " + rowC + " rows, we were expecting == 0: " + statement);
			}
			stmtC++;
		}
		return stmtC;
	}

	/**
	 * 添加创建表后的查询测试
	 */
	private static int doCreateTestQueries(DatabaseConnection connection, DatabaseType databaseType,
			List<String> queriesAfter) throws SQLException {
		int stmtC = 0;
		// now execute any test queries which test the newly created table
		for (String query : queriesAfter) {
			CompiledStatement compiledStmt = null;
			try {
				compiledStmt = connection.compileStatement(query, StatementType.SELECT, noFieldTypes,
						DatabaseConnection.DEFAULT_RESULT_FLAGS, false);
				// we don't care about an object cache here
				DatabaseResults results = compiledStmt.runQuery(null);
				int rowC = 0;
				// count the results
				for (boolean isThereMore = results.first(); isThereMore; isThereMore = results.next()) {
					rowC++;
				}
				logger.info("executing create table after-query got {} results: {}", rowC, query);
			} catch (SQLException e) {
				// we do this to make sure that the statement is in the exception
				throw SqlExceptionUtil.create("executing create table after-query failed: " + query, e);
			} finally {
				// result set is closed by the statement being closed
				IOUtils.closeThrowSqlException(compiledStmt, "compiled statement");
			}
			stmtC++;
		}
		return stmtC;
	}

	private static <T, ID> List<String> addCreateTableStatements(DatabaseType databaseType, TableInfo<T, ID> tableInfo,
			boolean createIfNotExists, boolean logDetails) throws SQLException {
		List<String> statements = new ArrayList<String>();
		List<String> queriesAfter = new ArrayList<String>();
		addCreateTableStatements(databaseType, tableInfo, statements, queriesAfter, createIfNotExists, logDetails);
		return statements;
	}

	/**
	 * Generate and return the list of statements to create a database table and any associated features.
	 * 生成创建表的SQL语句
	 */
	private static <T, ID> void addCreateTableStatements(DatabaseType databaseType, TableInfo<T, ID> tableInfo,
			List<String> statements, List<String> queriesAfter, boolean createIfNotExists, boolean logDetails)
			throws SQLException {
		StringBuilder sb = new StringBuilder(256);
		if (logDetails) {
			logger.info("creating table '{}'", tableInfo.getTableName());
		}
		//开始拼接SQL语句
		sb.append("CREATE TABLE ");
		if (createIfNotExists && databaseType.isCreateIfNotExistsSupported()) {
			sb.append("IF NOT EXISTS ");
		}
		if (tableInfo.getSchemaName() != null && tableInfo.getSchemaName().length() > 0) {
			databaseType.appendEscapedEntityName(sb, tableInfo.getSchemaName());
			sb.append('.');
		}
		//添加表名
		databaseType.appendEscapedEntityName(sb, tableInfo.getTableName());
		sb.append(" (");
		List<String> additionalArgs = new ArrayList<String>();
		List<String> statementsBefore = new ArrayList<String>();
		List<String> statementsAfter = new ArrayList<String>();
		// our statement will be set here later
		//用于标识是否第一个字段
		boolean first = true;
		//获取表实体上的字段
		for (FieldType fieldType : tableInfo.getFieldTypes()) {
			// skip foreign collections
			//跳过配置的外键的表数据集合字段
			if (fieldType.isForeignCollection()) {
				continue;
			} else if (first) {
				//第一个参数不要,号
				first = false;
			} else {
				sb.append(", ");
			}
			//获取列信息
			String columnDefinition = fieldType.getColumnDefinition();
			if (columnDefinition == null) {
				// we have to call back to the database type for the specific create syntax
				databaseType.appendColumnArg(tableInfo.getTableName(), sb, fieldType, additionalArgs, statementsBefore,
						statementsAfter, queriesAfter);
			} else {
				// hand defined field
				databaseType.appendEscapedEntityName(sb, fieldType.getColumnName());
				//添加列名
				sb.append(' ').append(columnDefinition).append(' ');
			}
		}
		//添加主键列SQL
		// add any sql that sets any primary key fields
		databaseType.addPrimaryKeySql(tableInfo.getFieldTypes(), additionalArgs, statementsBefore, statementsAfter,
				queriesAfter);
		//设置字段唯一SQL
		// add any sql that sets any unique fields
		databaseType.addUniqueComboSql(tableInfo.getFieldTypes(), additionalArgs, statementsBefore, statementsAfter,
				queriesAfter);
		for (String arg : additionalArgs) {
			//拼接参数
			// we will have spat out one argument already so we don't have to do the first dance
			sb.append(", ").append(arg);
		}
		//拼接结束
		sb.append(") ");
		databaseType.appendCreateTableSuffix(sb);
		statements.addAll(statementsBefore);
		statements.add(sb.toString());
		statements.addAll(statementsAfter);
		addCreateIndexStatements(databaseType, tableInfo, statements, createIfNotExists, false, logDetails);
		addCreateIndexStatements(databaseType, tableInfo, statements, createIfNotExists, true, logDetails);
	}

	/**
	 * 添加创建表索引SQL
	 */
	private static <T, ID> void addCreateIndexStatements(DatabaseType databaseType, TableInfo<T, ID> tableInfo,
			List<String> statements, boolean createIfNotExists, boolean unique, boolean logDetails) {
		// run through and look for index annotations
		Map<String, List<String>> indexMap = new HashMap<String, List<String>>();
		//获取数据库表实体上的字段
		for (FieldType fieldType : tableInfo.getFieldTypes()) {
			String indexName;
			//如果是唯一索引，则获取唯一索引的索引名
			if (unique) {
				indexName = fieldType.getUniqueIndexName();
			} else {
				//非唯一索引，获取索引名
				indexName = fieldType.getIndexName();
			}
			//不是索引字段，忽略
			if (indexName == null) {
				continue;
			}

			//按索引名，对列进行分组
			List<String> columnList = indexMap.get(indexName);
			if (columnList == null) {
				columnList = new ArrayList<String>();
				indexMap.put(indexName, columnList);
			}
			columnList.add(fieldType.getColumnName());
		}

		//开始拼接SQL语句
		StringBuilder sb = new StringBuilder(128);
		for (Map.Entry<String, List<String>> indexEntry : indexMap.entrySet()) {
			if (logDetails) {
				logger.info("creating index '{}' for table '{}", indexEntry.getKey(), tableInfo.getTableName());
			}
			sb.append("CREATE ");
			if (unique) {
				sb.append("UNIQUE ");
			}
			sb.append("INDEX ");
			if (createIfNotExists && databaseType.isCreateIndexIfNotExistsSupported()) {
				sb.append("IF NOT EXISTS ");
			}
			databaseType.appendEscapedEntityName(sb, indexEntry.getKey());
			sb.append(" ON ");
			databaseType.appendEscapedEntityName(sb, tableInfo.getTableName());
			sb.append(" ( ");
			boolean first = true;
			for (String columnName : indexEntry.getValue()) {
				if (first) {
					first = false;
				} else {
					sb.append(", ");
				}
				databaseType.appendEscapedEntityName(sb, columnName);
			}
			//拼接结束
			sb.append(" )");
			statements.add(sb.toString());
			sb.setLength(0);
		}
	}
}
